/**
 * @file Barrier.cpp
 *
 *  @date 2009-06-06
 *  @author hauleth
 */

#include <Bust/Threads/Barrier.hpp>
#include "PrivateData.hpp"

#include <pthread.h>

namespace bust {

static void barrier_cleanup_handler( void *arg ) {
	pthread_mutex_t *mutex = static_cast< pthread_mutex_t * > ( arg );
	pthread_mutex_unlock( mutex );
}

Barrier::Barrier( int numThreads ) : blockedNum(0) {
	BarrierPrivateData *pd = new BarrierPrivateData();
	pd->cnt = 0;
	pd->phase = 0;
	pd->maxcnt = numThreads;
	pthread_mutexattr_t mutex_attr;
	pthread_mutexattr_init( &mutex_attr );
	pthread_mutex_init( & ( pd->lock ), &mutex_attr );
	pthread_cond_init( & ( pd->cond ), NULL );
	_data = static_cast< void * > ( pd );
}

Barrier::~Barrier() {
	BarrierPrivateData *pd = static_cast< BarrierPrivateData* > ( _data );
	pthread_mutex_destroy( & ( pd->lock ) );
	pthread_cond_destroy( & ( pd->cond ) );
	delete pd;
}

void Barrier::reset() {
	BarrierPrivateData *pd = static_cast< BarrierPrivateData* > ( _data );
	pd->cnt = 0;
	pd->phase = 0;
}

void Barrier::block( unsigned int numThreads ) {

	BarrierPrivateData *pd =
			static_cast< BarrierPrivateData* > ( _data );
	if( numThreads != 0 )
		pd->maxcnt = numThreads;
	int my_phase;
	pthread_mutex_lock( & ( pd->lock ) );
	my_phase = pd->phase;
	++pd->cnt;
	if( pd->cnt == pd->maxcnt ) { // I am the last one
		pd->cnt = 0; // reset for next use
		pd->phase = 1 - my_phase; // toggle phase
		pthread_cond_broadcast( & ( pd->cond ) );
	}
	blockedNum++;
	while( pd->phase == my_phase ) {
		pthread_cleanup_push(barrier_cleanup_handler, &(pd->lock));
				pthread_cond_wait( & ( pd->cond ), & ( pd->lock ) );
				blockedNum--;
				pthread_cleanup_pop(0);
	}
	pthread_mutex_unlock( & ( pd->lock ) );
}

}
